// This code is derived from jcifs smb client library <jcifs at samba dot org>
// Ported by J. Arturo <webmaster at komodosoft dot net>
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
using System;
using SharpCifs.Util;
using SharpCifs.Util.Sharpen;

namespace SharpCifs.Smb
{
    /// <summary>To filter 0 len updates and for debugging</summary>
    public class SigningDigest
    {
        internal static LogStream Log = LogStream.GetInstance();

        private MessageDigest _digest;

        private byte[] _macSigningKey;

        private bool _bypass;

        private int _updates;

        private int _signSequence;

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        public SigningDigest(byte[] macSigningKey, bool bypass)
        {
            try
            {
                _digest = MessageDigest.GetInstance("MD5");
            }
            catch (NoSuchAlgorithmException ex)
            {
                if (Log.Level > 0)
                {
                    Runtime.PrintStackTrace(ex, Log);
                }
                throw new SmbException("MD5", ex);
            }
            this._macSigningKey = macSigningKey;
            this._bypass = bypass;
            _updates = 0;
            _signSequence = 0;
            if (Log.Level >= 5)
            {
                Log.WriteLine("macSigningKey:");
                Hexdump.ToHexdump(Log, macSigningKey, 0, macSigningKey.Length);
            }
        }

        /// <exception cref="SharpCifs.Smb.SmbException"></exception>
        public SigningDigest(SmbTransport transport, NtlmPasswordAuthentication auth)
        {
            try
            {
                _digest = MessageDigest.GetInstance("MD5");
            }
            catch (NoSuchAlgorithmException ex)
            {
                if (Log.Level > 0)
                {
                    Runtime.PrintStackTrace(ex, Log);
                }
                throw new SmbException("MD5", ex);
            }
            try
            {
                switch (SmbConstants.LmCompatibility)
                {
                    case 0:
                    case 1:
                    case 2:
                        {
                            _macSigningKey = new byte[40];
                            auth.GetUserSessionKey(transport.Server.EncryptionKey,
                                                   _macSigningKey,
                                                   0);
                            Array.Copy(auth.GetUnicodeHash(transport.Server.EncryptionKey),
                                       0,
                                       _macSigningKey,
                                       16,
                                       24);
                            break;
                        }

                    case 3:
                    case 4:
                    case 5:
                        {
                            _macSigningKey = new byte[16];
                            auth.GetUserSessionKey(transport.Server.EncryptionKey, _macSigningKey, 0);
                            break;
                        }

                    default:
                        {
                            _macSigningKey = new byte[40];
                            auth.GetUserSessionKey(transport.Server.EncryptionKey, _macSigningKey, 0);
                            Array.Copy(auth.GetUnicodeHash(transport.Server.EncryptionKey),
                                       0,
                                       _macSigningKey,
                                       16,
                                       24);
                            break;
                        }
                }
            }
            catch (Exception ex)
            {
                throw new SmbException(string.Empty, ex);
            }
            if (Log.Level >= 5)
            {
                Log.WriteLine("LM_COMPATIBILITY=" + SmbConstants.LmCompatibility);
                Hexdump.ToHexdump(Log, _macSigningKey, 0, _macSigningKey.Length);
            }
        }

        public virtual void Update(byte[] input, int offset, int len)
        {
            if (Log.Level >= 5)
            {
                Log.WriteLine("update: " + _updates + " " + offset + ":" + len);
                Hexdump.ToHexdump(Log, input, offset, Math.Min(len, 256));
                Log.Flush();
            }
            if (len == 0)
            {
                return;
            }
            _digest.Update(input, offset, len);
            _updates++;
        }

        public virtual byte[] Digest()
        {
            byte[] b;
            b = _digest.Digest();
            if (Log.Level >= 5)
            {
                Log.WriteLine("digest: ");
                Hexdump.ToHexdump(Log, b, 0, b.Length);
                Log.Flush();
            }
            _updates = 0;
            return b;
        }

        /// <summary>Performs MAC signing of the SMB.</summary>
        /// <remarks>
        /// Performs MAC signing of the SMB.  This is done as follows.
        /// The signature field of the SMB is overwritted with the sequence number;
        /// The MD5 digest of the MAC signing key + the entire SMB is taken;
        /// The first 8 bytes of this are placed in the signature field.
        /// </remarks>
        /// <param name="data">The data.</param>
        /// <param name="offset">The starting offset at which the SMB header begins.</param>
        /// <param name="length">The length of the SMB data starting at offset.</param>
        internal virtual void Sign(byte[] data,
                                   int offset,
                                   int length,
                                   ServerMessageBlock request,
                                   ServerMessageBlock response)
        {
            request.SignSeq = _signSequence;
            if (response != null)
            {
                response.SignSeq = _signSequence + 1;
                response.VerifyFailed = false;
            }
            try
            {
                Update(_macSigningKey, 0, _macSigningKey.Length);
                int index = offset + SmbConstants.SignatureOffset;
                for (int i = 0; i < 8; i++)
                {
                    data[index + i] = 0;
                }
                ServerMessageBlock.WriteInt4(_signSequence, data, index);
                Update(data, offset, length);
                Array.Copy(Digest(), 0, data, index, 8);
                if (_bypass)
                {
                    _bypass = false;
                    Array.Copy(Runtime.GetBytesForString("BSRSPYL "),
                               0,
                               data,
                               index,
                               8);
                }
            }
            catch (Exception ex)
            {
                if (Log.Level > 0)
                {
                    Runtime.PrintStackTrace(ex, Log);
                }
            }
            finally
            {
                _signSequence += 2;
            }
        }

        /// <summary>Performs MAC signature verification.</summary>
        /// <remarks>
        /// Performs MAC signature verification.  This calculates the signature
        /// of the SMB and compares it to the signature field on the SMB itself.
        /// </remarks>
        /// <param name="data">The data.</param>
        /// <param name="offset">The starting offset at which the SMB header begins.</param>
        /// <param name="length">The length of the SMB data starting at offset.</param>
        internal virtual bool Verify(byte[] data, int offset, ServerMessageBlock response)
        {
            Update(_macSigningKey, 0, _macSigningKey.Length);
            int index = offset;
            Update(data, index, SmbConstants.SignatureOffset);
            index += SmbConstants.SignatureOffset;
            byte[] sequence = new byte[8];
            ServerMessageBlock.WriteInt4(response.SignSeq, sequence, 0);
            Update(sequence, 0, sequence.Length);
            index += 8;
            if (response.Command == ServerMessageBlock.SmbComReadAndx)
            {
                SmbComReadAndXResponse raxr = (SmbComReadAndXResponse)response;
                int length = response.Length - raxr.DataLength;
                Update(data, index, length - SmbConstants.SignatureOffset - 8);
                Update(raxr.B, raxr.Off, raxr.DataLength);
            }
            else
            {
                Update(data, index, response.Length - SmbConstants.SignatureOffset - 8);
            }
            byte[] signature = Digest();
            for (int i = 0; i < 8; i++)
            {
                if (signature[i] != data[offset + SmbConstants.SignatureOffset + i])
                {
                    if (Log.Level >= 2)
                    {
                        Log.WriteLine("signature verification failure");
                        Hexdump.ToHexdump(Log, signature, 0, 8);
                        Hexdump.ToHexdump(Log, data, offset + SmbConstants.SignatureOffset, 8);
                    }
                    return response.VerifyFailed = true;
                }
            }
            return response.VerifyFailed = false;
        }

        public override string ToString()
        {
            return "LM_COMPATIBILITY=" + SmbConstants.LmCompatibility
                    + " MacSigningKey=" + Hexdump.ToHexString(_macSigningKey,
                                                              0,
                                                              _macSigningKey.Length);
        }
    }
}
